// SPDX-License-Identifier: GPL-2.0-only
/*
 * HiSilicon SoC L3C uncore Hardware event counters support
 *
 * Copyright (C) 2017 Hisilicon Limited
 * Author: Anurup M <anurup.m@huawei.com>
 *         Shaokun Zhang <zhangshaokun@hisilicon.com>
 *
 * This code is based on the uncore PMUs like arm-cci and arm-ccn.
 */
#include <linux/acpi.h>
#include <linux/bug.h>
#include <linux/cpuhotplug.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/list.h>
#include <linux/smp.h>

#include "hisi_uncore_pmu.h"

/*
 * ARMv8 HiSilicon L3C event types.
 */
enum armv8_hisi_l3c_event_types {
	HISI_HWEVENT_L3C_READ_ALLOCATE		= 0x0,
	HISI_HWEVENT_L3C_WRITE_ALLOCATE		= 0x01,
	HISI_HWEVENT_L3C_READ_NOALLOCATE	= 0x02,
	HISI_HWEVENT_L3C_WRITE_NOALLOCATE	= 0x03,
	HISI_HWEVENT_L3C_READ_HIT		= 0x04,
	HISI_HWEVENT_L3C_WRITE_HIT		= 0x05,
	HISI_HWEVENT_L3C_DSID0			= 0x30,
	HISI_HWEVENT_L3C_DSID1			= 0x31,
	HISI_HWEVENT_L3C_DSID2			= 0x32,
	HISI_HWEVENT_L3C_DSID3			= 0x33,
	HISI_HWEVENT_L3C_DSID4			= 0x34,
	HISI_HWEVENT_L3C_DSID5			= 0x35,
	HISI_HWEVENT_L3C_DSID6			= 0x36,
	HISI_HWEVENT_L3C_DSID7			= 0x37,
	HISI_HWEVENT_L3C_EVENT_MAX		= 0x41,
};

/* L3C register definition */
#define L3C_PERF_CTRL		0x04
#define L3C_INT_MASK		0x0800
#define L3C_INT_STATUS		0x0808
#define L3C_INT_CLEAR		0x080c
#define L3C_EVENT_CTRL	        0x1c00
#define L3C_EVENT_TYPE0		0x140
#define L3C_CNTR0_LOWER         0x170

/* L3C has 8-counters */
#define L3C_NR_COUNTERS		0x8

#define L3C_PERF_CTRL_EN	0x1000000
#define L3C_EVTYPE_NONE		0xff

#define L3C_NR_EVENTS		0x59

#define L3C_DSID_PART_REG(idx)	(0x40 + ((idx) / 4) * 0x4)
#define LLC_AA_CPU_PART(n)	(0x00040 + (n) * 0x4)
#define LLC_AA_PART_MODE	(0x00090)

#define L3C_HRTIMER_INTERVAL	(10LL * MSEC_PER_SEC)
#define HISI_MAX_PERIOD(nr)	(BIT_ULL(nr) - 1)

static u32 hisi_l3c_pmu_mini_get_counter_offset(int cntr_idx)
{
	return (L3C_CNTR0_LOWER + (cntr_idx * 4));
}

static u64 hisi_l3c_pmu_mini_read_counter(struct hisi_pmu *l3c_pmu_mini,
				     struct hw_perf_event *hwc)
{
	return readl(l3c_pmu_mini->base + hisi_l3c_pmu_mini_get_counter_offset(hwc->idx));
}

static void hisi_l3c_pmu_mini_write_counter(struct hisi_pmu *l3c_pmu_mini,
				       struct hw_perf_event *hwc, u64 val)
{
	writel(val, l3c_pmu_mini->base + hisi_l3c_pmu_mini_get_counter_offset(hwc->idx));
}

static bool hisi_l3c_pmu_mini_cal_dsid_delta(struct perf_event *event)
{
	int event_id = event->hw.config_base & 0xff;
	return event_id >= HISI_HWEVENT_L3C_DSID0  && event_id <= HISI_HWEVENT_L3C_DSID7;
}

static void hisi_l3c_pmu_mini_write_evtype(struct hisi_pmu *l3c_pmu_mini, int idx,
				      u32 type)
{
	u32 reg, reg_idx, shift, val;

	/*
	 * Select the appropriate event select register(L3C_EVENT_TYPE0/1).
	 * There are 2 event select registers for the 8 hardware counters.
	 * Event code is 8-bits and for the former 4 hardware counters,
	 * L3C_EVENT_TYPE0 is chosen. For the latter 4 hardware counters,
	 * L3C_EVENT_TYPE1 is chosen.
	 */
	reg = L3C_EVENT_TYPE0 + (idx / 4) * 4;
	reg_idx = idx % 4;
	shift = 8 * reg_idx;

	/* Write event code to L3C_EVENT_TYPEx Register */
	val = readl(l3c_pmu_mini->base + reg);
	val &= ~(L3C_EVTYPE_NONE << shift);
	val |= (type << shift);
	writel(val, l3c_pmu_mini->base + reg);

	writel(0x4, l3c_pmu_mini->aa_base + LLC_AA_PART_MODE);
	writel(0xf10000, l3c_pmu_mini->aa_base + LLC_AA_CPU_PART(0));
	writel(0xf30000, l3c_pmu_mini->aa_base + LLC_AA_CPU_PART(1));
	writel(0xf50000, l3c_pmu_mini->aa_base + LLC_AA_CPU_PART(2));
	writel(0xf70000, l3c_pmu_mini->aa_base + LLC_AA_CPU_PART(3));
	writel(0xf90000, l3c_pmu_mini->aa_base + LLC_AA_CPU_PART(4));
	writel(0xfb0000, l3c_pmu_mini->aa_base + LLC_AA_CPU_PART(5));
	writel(0xfd0000, l3c_pmu_mini->aa_base + LLC_AA_CPU_PART(6));
	writel(0xff0000, l3c_pmu_mini->aa_base + LLC_AA_CPU_PART(7));
	writel(0xf7f5f3f1, l3c_pmu_mini->base + L3C_DSID_PART_REG(0));
	writel(0xfffdfbf9, l3c_pmu_mini->base + L3C_DSID_PART_REG(1));
}

static void hisi_l3c_pmu_mini_clear_evtype(struct hisi_pmu *l3c_pmu_mini, int idx)
{
	u32 reg, reg_idx, shift, val;

	/*
	 * Clear the event in L3C_EVENT_TYPEx Register
	 * Each byte in the 32 bit event select register is used to configure
	 * the event code. Each byte correspond to a counter register to use.
	 * Use (idx % 4) to select the byte to clear in event select register
	 * with the vale 0xff.
	 */

	reg = L3C_EVENT_TYPE0 + (idx / 4) * 4;
	reg_idx = idx % 4;
	shift = 8 * reg_idx;

	/* Write event code to L3C_EVENT_TYPEx Register */
	val = readl(l3c_pmu_mini->base + reg);
	val &= ~(L3C_EVTYPE_NONE << shift);
	val |= (L3C_EVTYPE_NONE << shift);
	writel(val, l3c_pmu_mini->base + reg);

	writel(0x0, l3c_pmu_mini->aa_base + LLC_AA_PART_MODE);
	writel(0x0, l3c_pmu_mini->aa_base + LLC_AA_CPU_PART(0));
	writel(0x0, l3c_pmu_mini->aa_base + LLC_AA_CPU_PART(1));
	writel(0x0, l3c_pmu_mini->aa_base + LLC_AA_CPU_PART(2));
	writel(0x0, l3c_pmu_mini->aa_base + LLC_AA_CPU_PART(3));
	writel(0x0, l3c_pmu_mini->aa_base + LLC_AA_CPU_PART(4));
	writel(0x0, l3c_pmu_mini->aa_base + LLC_AA_CPU_PART(5));
	writel(0x0, l3c_pmu_mini->aa_base + LLC_AA_CPU_PART(6));
	writel(0x0, l3c_pmu_mini->aa_base + LLC_AA_CPU_PART(7));
	writel(0x0, l3c_pmu_mini->base + L3C_DSID_PART_REG(0));
	writel(0x0, l3c_pmu_mini->base + L3C_DSID_PART_REG(1));
}

static void hisi_l3c_pmu_mini_start_counters(struct hisi_pmu *l3c_pmu_mini)
{
	u32 val;

	/*
	 * Set perf_enable bit in L3C_PERF_CTRL register to start counting
	 * for all enabled counters.
	 */
	val = readl(l3c_pmu_mini->base + L3C_PERF_CTRL);
	val |= L3C_PERF_CTRL_EN;
	writel(val, l3c_pmu_mini->base + L3C_PERF_CTRL);
}

static void hisi_l3c_pmu_mini_stop_counters(struct hisi_pmu *l3c_pmu_mini)
{
	u32 val;

	/*
	 * Clear perf_enable bit in L3C_PERF_CTRL register to stop counting
	 * for all enabled counters.
	 */
	val = readl(l3c_pmu_mini->base + L3C_PERF_CTRL);
	val &= ~(L3C_PERF_CTRL_EN);
	writel(val, l3c_pmu_mini->base + L3C_PERF_CTRL);
}

static void hisi_l3c_pmu_mini_enable_counter(struct hisi_pmu *l3c_pmu_mini,
					struct hw_perf_event *hwc)
{
	u32 val;

	/* Enable counter index in L3C_EVENT_CTRL register */
	val = readl(l3c_pmu_mini->base + L3C_EVENT_CTRL);
	val |= (1 << hwc->idx);
	writel(val, l3c_pmu_mini->base + L3C_EVENT_CTRL);
}

static void hisi_l3c_pmu_mini_disable_counter(struct hisi_pmu *l3c_pmu_mini,
					 struct hw_perf_event *hwc)
{
	u32 val;

	/* Clear counter index in L3C_EVENT_CTRL register */
	val = readl(l3c_pmu_mini->base + L3C_EVENT_CTRL);
	val &= ~(1 << hwc->idx);
	writel(val, l3c_pmu_mini->base + L3C_EVENT_CTRL);
}

static void hisi_l3c_pmu_mini_enable_counter_int(struct hisi_pmu *l3c_pmu_mini,
					    struct hw_perf_event *hwc)
{
	u32 val;

	val = readl(l3c_pmu_mini->base + L3C_INT_MASK);
	/* Write 0 to enable interrupt */
	val &= ~(1 << hwc->idx);
	writel(val, l3c_pmu_mini->base + L3C_INT_MASK);
}

static void hisi_l3c_pmu_mini_disable_counter_int(struct hisi_pmu *l3c_pmu_mini,
					     struct hw_perf_event *hwc)
{
	u32 val;

	val = readl(l3c_pmu_mini->base + L3C_INT_MASK);
	/* Write 1 to mask interrupt */
	val |= (1 << hwc->idx);
	writel(val, l3c_pmu_mini->base + L3C_INT_MASK);
}

static u32 hisi_l3c_pmu_mini_get_int_status(struct hisi_pmu *l3c_pmu_mini)
{
	return readl(l3c_pmu_mini->base + L3C_INT_STATUS);
}

static void hisi_l3c_pmu_mini_clear_int_status(struct hisi_pmu *l3c_pmu_mini, int idx)
{
	writel(1 << idx, l3c_pmu_mini->base + L3C_INT_CLEAR);
}

static const struct acpi_device_id hisi_l3c_pmu_mini_acpi_match[] = {
	{}
};
MODULE_DEVICE_TABLE(acpi, hisi_l3c_pmu_mini_acpi_match);

static const struct of_device_id l3c_of_match[] = {
	{ .compatible = "hisilicon,l3c-pmu-mini", },
	{},
};
MODULE_DEVICE_TABLE(of, l3c_of_match);

static int hisi_l3c_pmu_mini_init_data(struct platform_device *pdev,
				  struct hisi_pmu *l3c_pmu_mini)
{
	/*
	 * Use the SCCL_ID and CCL_ID to identify the L3C PMU, while
	 * SCCL_ID is in MPIDR[aff2] and CCL_ID is in MPIDR[aff1].
	 */
	if (device_property_read_u32(&pdev->dev, "hisilicon,scl-id",
				     &l3c_pmu_mini->sccl_id)) {
		dev_err(&pdev->dev, "Can not read l3c sccl-id!\n");
		return -EINVAL;
	}

	if (device_property_read_u32(&pdev->dev, "hisilicon,ccl-id",
				     &l3c_pmu_mini->ccl_id)) {
		dev_err(&pdev->dev, "Can not read l3c ccl-id!\n");
		return -EINVAL;
	}

	l3c_pmu_mini->base = devm_platform_ioremap_resource(pdev, 0);
	if (IS_ERR(l3c_pmu_mini->base)) {
		dev_err(&pdev->dev, "ioremap failed for l3c_pmu resource\n");
		return PTR_ERR(l3c_pmu_mini->base);
	}

	return 0;
}

static struct attribute *hisi_l3c_pmu_mini_format_attr[] = {
	HISI_PMU_FORMAT_ATTR(event, "config:0-7"),
	NULL,
};

static const struct attribute_group hisi_l3c_pmu_mini_format_group = {
	.name = "format",
	.attrs = hisi_l3c_pmu_mini_format_attr,
};

static struct attribute *hisi_l3c_pmu_mini_events_attr[] = {
	HISI_PMU_EVENT_ATTR(read_allocate,	0x00),
	HISI_PMU_EVENT_ATTR(write_allocate,	0x01),
	HISI_PMU_EVENT_ATTR(read_noallocate,	0x02),
	HISI_PMU_EVENT_ATTR(write_noallocate,	0x03),
	HISI_PMU_EVENT_ATTR(read_hit,		0x04),
	HISI_PMU_EVENT_ATTR(write_hit,		0x05),
	HISI_PMU_EVENT_ATTR(dsid0,		0x30),
	HISI_PMU_EVENT_ATTR(dsid1,		0x31),
	HISI_PMU_EVENT_ATTR(dsid2,		0x32),
	HISI_PMU_EVENT_ATTR(dsid3,		0x33),
	HISI_PMU_EVENT_ATTR(dsid4,		0x34),
	HISI_PMU_EVENT_ATTR(dsid5,		0x35),
	HISI_PMU_EVENT_ATTR(dsid6,		0x36),
	HISI_PMU_EVENT_ATTR(dsid7,		0x37),
	NULL,
};

static const struct attribute_group hisi_l3c_pmu_mini_events_group = {
	.name = "events",
	.attrs = hisi_l3c_pmu_mini_events_attr,
};

static DEVICE_ATTR(cpumask, 0444, hisi_cpumask_sysfs_show, NULL);

static struct attribute *hisi_l3c_pmu_mini_cpumask_attrs[] = {
	&dev_attr_cpumask.attr,
	NULL,
};

static const struct attribute_group hisi_l3c_pmu_mini_cpumask_attr_group = {
	.attrs = hisi_l3c_pmu_mini_cpumask_attrs,
};

static const struct attribute_group *hisi_l3c_pmu_mini_attr_groups[] = {
	&hisi_l3c_pmu_mini_format_group,
	&hisi_l3c_pmu_mini_events_group,
	&hisi_l3c_pmu_mini_cpumask_attr_group,
	NULL,
};

static enum hrtimer_restart hisi_hrtimer_callback_llc(struct hrtimer *hrtimer)
{
	struct hisi_pmu *hisi_pmu = container_of(hrtimer,
						struct hisi_pmu, hrtimer);
	struct perf_event *event = NULL;
	unsigned long flags;
	int event_id;

	/* Return if no active events */
	if (!hisi_pmu->num_active)
		return HRTIMER_NORESTART;

	local_irq_save(flags);

	/* Update event count for each active event */
	list_for_each_entry(event, &hisi_pmu->active_list, active_entry) {
		/* Read hardware counter and update the Perf event counter */
		/* disd counts show the amount of cache, no increase */
		event_id = event->hw.config & 0xff;
		if (event_id < HISI_HWEVENT_L3C_DSID0)
			hisi_uncore_pmu_event_update(event);
	}

	local_irq_restore(flags);
	hrtimer_forward_now(hrtimer, ms_to_ktime(hisi_pmu->hrt_duration));
	return HRTIMER_RESTART;
}

void hisi_hrtimer_init_llc(struct hisi_pmu *hisi_pmu_mini, u64 timer_interval)
{
	/* hr timer clock initalization */
	hrtimer_init(&hisi_pmu_mini->hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
	hisi_pmu_mini->hrtimer.function = &hisi_hrtimer_callback_llc;
	hisi_pmu_mini->hrt_duration = timer_interval;
}

static const struct hisi_uncore_ops hisi_uncore_l3c_ops = {
	.write_evtype		= hisi_l3c_pmu_mini_write_evtype,
	.clear_evtype		= hisi_l3c_pmu_mini_clear_evtype,
	.get_event_idx		= hisi_uncore_pmu_get_event_idx,
	.start_counters		= hisi_l3c_pmu_mini_start_counters,
	.stop_counters		= hisi_l3c_pmu_mini_stop_counters,
	.enable_counter		= hisi_l3c_pmu_mini_enable_counter,
	.disable_counter	= hisi_l3c_pmu_mini_disable_counter,
	.enable_counter_int	= hisi_l3c_pmu_mini_enable_counter_int,
	.disable_counter_int	= hisi_l3c_pmu_mini_disable_counter_int,
	.write_counter		= hisi_l3c_pmu_mini_write_counter,
	.read_counter		= hisi_l3c_pmu_mini_read_counter,
	.get_int_status		= hisi_l3c_pmu_mini_get_int_status,
	.clear_int_status	= hisi_l3c_pmu_mini_clear_int_status,
	.start_hrtimer          = hisi_hrtimer_start,
	.stop_hrtimer           = hisi_hrtimer_stop,
	.cal_dsid_delta         = hisi_l3c_pmu_mini_cal_dsid_delta,
};

/* Initialize hrtimer to poll for avoiding counter overflow */
static void hisi_l3c_pmu_mini_hrtimer_init(struct hisi_pmu *l3c_pmu_mini)
{
	INIT_LIST_HEAD(&l3c_pmu_mini->active_list);
	hisi_hrtimer_init_llc(l3c_pmu_mini, L3C_HRTIMER_INTERVAL);
}

static int hisi_l3c_pmu_mini_dev_probe(struct platform_device *pdev,
				  struct hisi_pmu *l3c_pmu_mini)
{
	int ret;

	ret = hisi_l3c_pmu_mini_init_data(pdev, l3c_pmu_mini);
	if (ret)
		return ret;

	ret = hisi_uncore_pmu_init_irq(l3c_pmu_mini, pdev);
	if (ret)
		return ret;

	l3c_pmu_mini->counter_bits = 32;
	l3c_pmu_mini->check_event = L3C_NR_EVENTS;
	l3c_pmu_mini->pmu_events.attr_groups = hisi_l3c_pmu_mini_attr_groups;
	l3c_pmu_mini->num_counters = L3C_NR_COUNTERS;
	l3c_pmu_mini->ops = &hisi_uncore_l3c_ops;
	l3c_pmu_mini->dev = &pdev->dev;
	l3c_pmu_mini->on_cpu = -1;

	l3c_pmu_mini->num_active = 0;
	l3c_pmu_mini->aa_base = ioremap(0x100120000, 0x10000);
	/*
	 * Use poll method to avoid counter overflow as overflow IRQ
	 * is not supported in v1,v2 hardware.
	 */
	hisi_l3c_pmu_mini_hrtimer_init(l3c_pmu_mini);

	return 0;
}

static int hisi_l3c_pmu_mini_probe(struct platform_device *pdev)
{
	struct hisi_pmu *l3c_pmu_mini;
	char *name;
	int ret;

	l3c_pmu_mini = devm_kzalloc(&pdev->dev, sizeof(*l3c_pmu_mini), GFP_KERNEL);
	if (!l3c_pmu_mini)
		return -ENOMEM;

	platform_set_drvdata(pdev, l3c_pmu_mini);

	ret = hisi_l3c_pmu_mini_dev_probe(pdev, l3c_pmu_mini);
	if (ret)
		return ret;

	ret = cpuhp_state_add_instance(CPUHP_AP_PERF_ARM_HISI_L3C_MINI_ONLINE,
				       &l3c_pmu_mini->node);
	if (ret) {
		dev_err(&pdev->dev, "Error %d registering hotplug\n", ret);
		return ret;
	}

	/*
	 * CCL_ID is used to identify the L3C in the same SCCL which was
	 * used _UID by mistake.
	 */
	name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "hisi_l3c%u_%u",
			      l3c_pmu_mini->index_id, l3c_pmu_mini->sccl_id);
	l3c_pmu_mini->pmu = (struct pmu) {
		.name		= name,
		.module		= THIS_MODULE,
		.task_ctx_nr	= perf_invalid_context,
		.event_init	= hisi_uncore_pmu_event_init,
		.pmu_enable	= hisi_uncore_pmu_enable,
		.pmu_disable	= hisi_uncore_pmu_disable,
		.add		= hisi_uncore_pmu_add,
		.del		= hisi_uncore_pmu_del,
		.start		= hisi_uncore_pmu_start,
		.stop		= hisi_uncore_pmu_stop,
		.read		= hisi_uncore_pmu_read,
		.attr_groups	= l3c_pmu_mini->pmu_events.attr_groups,
		.capabilities	= PERF_PMU_CAP_NO_EXCLUDE,
	};

	ret = perf_pmu_register(&l3c_pmu_mini->pmu, name, -1);
	if (ret) {
		dev_err(l3c_pmu_mini->dev, "L3C PMU register failed!\n");
		cpuhp_state_remove_instance_nocalls(
			CPUHP_AP_PERF_ARM_HISI_L3C_MINI_ONLINE, &l3c_pmu_mini->node);
	}

	return ret;
}

static int hisi_l3c_pmu_mini_remove(struct platform_device *pdev)
{
	struct hisi_pmu *l3c_pmu_mini = platform_get_drvdata(pdev);

	perf_pmu_unregister(&l3c_pmu_mini->pmu);
	cpuhp_state_remove_instance_nocalls(CPUHP_AP_PERF_ARM_HISI_L3C_MINI_ONLINE,
					    &l3c_pmu_mini->node);
	return 0;
}

static struct platform_driver hisi_l3c_pmu_mini_driver = {
	.driver = {
		.name = "hisi_l3c_pmu_mini",
		.acpi_match_table = ACPI_PTR(hisi_l3c_pmu_mini_acpi_match),
		.of_match_table = of_match_ptr(l3c_of_match),
		.suppress_bind_attrs = true,
	},
	.probe = hisi_l3c_pmu_mini_probe,
	.remove = hisi_l3c_pmu_mini_remove,
};

static int __init hisi_l3c_pmu_mini_module_init(void)
{
	int ret;

	ret = cpuhp_setup_state_multi(CPUHP_AP_PERF_ARM_HISI_L3C_MINI_ONLINE,
				      "AP_PERF_ARM_HISI_L3C_MINI_ONLINE",
				      hisi_uncore_pmu_online_cpu,
				      hisi_uncore_pmu_offline_cpu);
	if (ret) {
		pr_err("L3C PMU: Error setup hotplug, ret = %d\n", ret);
		return ret;
	}

	ret = platform_driver_register(&hisi_l3c_pmu_mini_driver);
	if (ret)
		cpuhp_remove_multi_state(CPUHP_AP_PERF_ARM_HISI_L3C_MINI_ONLINE);

	return ret;
}
module_init(hisi_l3c_pmu_mini_module_init);

static void __exit hisi_l3c_pmu_mini_module_exit(void)
{
	platform_driver_unregister(&hisi_l3c_pmu_mini_driver);
	cpuhp_remove_multi_state(CPUHP_AP_PERF_ARM_HISI_L3C_MINI_ONLINE);
}
module_exit(hisi_l3c_pmu_mini_module_exit);

MODULE_DESCRIPTION("HiSilicon SoC L3C uncore PMU mini driver");
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Anurup M <anurup.m@huawei.com>");
MODULE_AUTHOR("Shaokun Zhang <zhangshaokun@hisilicon.com>");
